package dryven.codegen.asm;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.util.CheckClassAdapter;
import org.objectweb.asm.util.TraceClassVisitor;

import dryven.codegen.asm.typeinfo.ClassTypeInfo;
import dryven.codegen.asm.typeinfo.TypeInfo;
import dryven.codegen.asm.typeinfo.UncompiledClassTypeInfo;
import dryven.model.binding.proxy.BindingProxy;

public class ClassBuilder {
	
	private ClassWriter _cw;
	private ClassVisitor _cv;
	private TypeInfo _classType;
	private StringWriter _out;
	
	public ClassBuilder(String className) {
		this(className,Object.class);
	}
	
	public ClassBuilder(String className, Class<?> superType) {
		this(className,superType,null);
	}
	
	public ClassBuilder(String className, Class<?> superType, Class<?>[] interfaces) {
		TypeInfo superTypeInfo = new ClassTypeInfo(superType);
		_classType = new UncompiledClassTypeInfo(className,superTypeInfo);
		String ifaces[] = null;
		if(interfaces!=null && interfaces.length!=0) {
			ifaces = new String[interfaces.length];
			for(int i=0;i<interfaces.length;++i) {
				ifaces[i] = new ClassTypeInfo(interfaces[i]).getInternalClassName();
			}
		}
		
		_cw = new ClassWriter(0);
		
		//_out = new StringWriter();
		//_cv = new TraceClassVisitor(_cw,new PrintWriter(_out));//new CheckClassAdapter(_cw);
		//_cv = new CheckClassAdapter(_cw);
		_cv = _cw;
		
		_cv.visit(
				Opcodes.V1_6, 
				Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, 
				_classType.getInternalClassName(), 
				null, 
				superTypeInfo.getInternalClassName(), 
				ifaces
			);
	}
	
	public byte[] finish() {
		_cv.visitEnd();
		//System.out.println(_out.toString());
		return _cw.toByteArray();
	}
	
	public MethodBuilder buildConstructor(Access access, Class<?> ... params) {
		return buildMethod("<init>", access, Void.TYPE, params);
	}
	
	public MethodBuilder buildMethod(String name, Access access, Class<?> returnType, Class<?> ... params) {
		StringBuilder signature = new StringBuilder();
		signature.append('(');
		TypeInfo paramTypeInfos[] = new TypeInfo[params.length];
		for(int i = 0;i<params.length;++i) {
			TypeInfo ti = new ClassTypeInfo(params[i]);
			signature.append(ti.getTypeDescriptor());
			paramTypeInfos[i] = ti;
		}
		TypeInfo returnTypeInfo = new ClassTypeInfo(returnType);
		signature.append(')');
		signature.append(returnTypeInfo.getTypeDescriptor());
		MethodVisitor mv = _cv.visitMethod(access.getOpcode(), name, signature.toString(), null, null);
		mv.visitCode();
		return new MethodBuilder(mv, _classType, returnTypeInfo, paramTypeInfos);
	}
	
	public FieldDefinition buildField(String name, Access access, Class<?> type) {
		TypeInfo typeInfo = new ClassTypeInfo(type);
		FieldVisitor fv = _cv.visitField(access.getOpcode(), name, typeInfo.getTypeDescriptor(), null, null);
		fv.visitEnd();
		return new FieldDefinition(typeInfo, name, _classType);
	}
}
